import $ from 'jquery';
import md5 from 'blueimp-md5';

class FileUpload {
  constructor(options) {
    this._options = $.extend(
      {
        maxUploadingThreads: 2,
        chunkSizeInMB: 1,
        browserNotSupport: $.noop,
        fileSelected: $.noop,
        fileReadStart: $.noop,
        fileReadProgress: $.noop,
        fileReadDone: $.noop,
        chunkUploadProgress: $.noop,
        chunkUploadDone: $.noop,
        chunkUploadError: $.noop,
        fileUploadDone: $.noop,
        fileUploadError: $.noop,

        urlStartFiles: '',
        urlUploadChunk: '',
        urlAssembleFile: '',
      },
      options,
    );
    this._fileInput = null;
    this._file = null;
    this._uploadingThreads = 0;
    this._uploadingChunks = [];
    this._uploadingInterval = 0;

    if (!this.supportH5()) {
      this._options.browserNotSupport.apply(null);
      return;
    }

    this.init();
  }
}

$.extend(FileUpload.prototype, {
  supportH5: function() {
    return window.File && window.FileReader && window.FileList && window.Blob;
  },
  init: function() {
    if (this._options.file == null) {
      throw Error('file is null');
    }
    this._setFile(this._options.file);
  },
  _clear: function() {
    this._file = null;
    this._uploadingThreads = 0;
    window.clearInterval(this._uploadingInterval);
    $.each(this._uploadingChunks, function() {
      this.xhr && this.xhr.abort();
    });
    this._uploadingChunks.length = 0;
  },
  _triggerFileChunksAllUploadDoneWhenReady: function(file) {
    var that = this,
      uploadedChunks = [];
    $.each(file.chunks, function() {
      if (this.uploaded) {
        uploadedChunks.push(this.id);
      }
    });
    if (uploadedChunks.length < file.chunks.length) {
      return;
    }

    var sendData = {
      size: file.size,
      chunkNames: uploadedChunks,
      name: file.name,
    };
    $.post(
      that._options.urlAssembleFile,
      { data: JSON.stringify(sendData) },
      function(data) {
        if (data.status == 1) {
          that._options.fileUploadDone.call(file, data.data);
        }
      },
      'json',
    );
  },
  _setFile: function(fileObject) {
    var file = {
      id: md5(fileObject.name + '-' + fileObject.size),
      name: fileObject.name,
      size: fileObject.size,
      sizeKB: Math.round((fileObject.size * 100) / 1024) / 100,
      sizeMB: Math.round((fileObject.size * 100) / (1024 * 1024)) / 100,
      bytes: null,
      chunks: [],
    };
    this._file = file;
    this._options.fileSelected.apply(file);
    this._readOneFile(file, fileObject);
    return file;
  },
  _readOneFile: function(file, fileObject) {
    var that = this,
      reader = new FileReader();
    reader.onloadstart = function() {
      that._options.fileReadStart.apply(file);
    };
    reader.onprogress = function(e) {
      if (e.lengthComputable) {
        var percent = Math.round((e.loaded * 100) / e.total);
        that._options.fileReadProgress.apply(file, [percent]);
      }
    };
    reader.onload = function() {
      file.bytes = reader.result;
      that._createChunks(file);
      that._options.fileReadDone.apply(file);
      that._sendFile();
    };
    reader.readAsBinaryString(fileObject);
  },
  _createChunks: function(file) {
    var start = 0,
      chunkSize = this._options.chunkSizeInMB * 1024 * 1024,
      byteLength = file.bytes.length,
      done = false;

    do {
      var offset, end;
      if (chunkSize + start < byteLength) {
        offset = start;
        end = start + chunkSize;
        done = false;
      } else {
        offset = start;
        end = byteLength;
        done = true;
      }
      file.chunks.push({
        id: file.id + '-' + offset + '-' + end,
        uploaded: false,
        file: file,
        offset: offset,
        end: end,
        xhr: null,
      });
      start += chunkSize;
    } while (!done);
  },
  _sendFile: function() {
    var that = this;
    var chunks = [];
    $.each(that._file.chunks, function() {
      chunks.push(this.id);
    });
    $.post(
      that._options.urlStartFiles,
      {
        data: JSON.stringify({ chunkNames: chunks }),
      },
      function(data) {
        if (data.status == 1) {
          $.each(that._file.chunks, function() {
            var chunk = this;
            $.each(data.data.list, function() {
              if (this == chunk.id) {
                chunk.uploaded = true;
                that._options.chunkUploadDone.apply(chunk);
                that._triggerFileChunksAllUploadDoneWhenReady(chunk.file);
                return false;
              }
            });
          });
          that._queueSendFiles();
        }
      },
      'json',
    );
  },
  _queueSendFiles: function() {
    var that = this;
    $.each(this._file.chunks, function() {
      if (!this.uploaded) {
        that._uploadingChunks.push(this);
      }
    });
    this._uploadChunks();
  },
  _uploadChunks: function() {
    var that = this;
    this._uploadingInterval = window.setInterval(function() {
      if (that._uploadingChunks.length > 0) {
        that._uploadChunk();
      } else {
        window.clearInterval(that._uploadingInterval);
      }
    }, 100);
  },
  _uploadChunk: function() {
    var that = this;
    if (this._uploadingChunks.length <= 0) {
      return;
    }
    if (this._uploadingThreads >= this._options.maxUploadingThreads) {
      return;
    }
    this._uploadingThreads++;
    var chunk = this._uploadingChunks.shift(),
      xhr = new XMLHttpRequest(),
      data = btoa(chunk.file.bytes.substring(chunk.offset, chunk.end));

    chunk.xhr = xhr;

    xhr.upload.addEventListener(
      'progress',
      function(e) {
        if (e.lengthComputable) {
          var percent = Math.round((e.loaded * 100) / e.total);
          that._options.chunkUploadProgress.apply(chunk, [percent]);
        }
      },
      false,
    );
    xhr.addEventListener(
      'load',
      function(e) {
        var data = JSON.parse(e.target.responseText);
        if (data.status == 1) {
          chunk.uploaded = true;
          that._options.chunkUploadDone.apply(chunk);
          that._triggerFileChunksAllUploadDoneWhenReady(chunk.file);
        } else {
          that._options.chunkUploadError.apply(chunk, [data]);
        }
        chunk.xhr = null;
        that._uploadingThreads--;
      },
      false,
    );
    xhr.open('POST', that._options.urlUploadChunk);
    xhr.send(
      'data=' +
        encodeURIComponent(data) +
        '&chunkName=' +
        encodeURIComponent(chunk.id),
    );
  },
});

function fileUpload(options) {
  return new FileUpload(options);
}

export default fileUpload;
